From 2bed470f678ba4b042e9b7605cddb48825fa09ab Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Fri, 6 Mar 2026 15:32:29 +0200 Subject: [PATCH] [PATCH 13/24] lib-imap: Add imap_parser_params.list_count_limit Gbp-Pq: Name CVE-2026-27857-3.patch --- src/lib-imap/imap-parser.c | 15 ++++++++++- src/lib-imap/imap-parser.h | 6 +++++ src/lib-imap/test-imap-parser.c | 46 +++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/lib-imap/imap-parser.c b/src/lib-imap/imap-parser.c index 532cb97..6212aed 100644 --- a/src/lib-imap/imap-parser.c +++ b/src/lib-imap/imap-parser.c @@ -39,6 +39,7 @@ struct imap_parser { struct istream *input; struct ostream *output; size_t max_line_size; + unsigned int list_count_limit; enum imap_parser_flags flags; /* reset by imap_parser_reset(): */ @@ -46,6 +47,7 @@ struct imap_parser { ARRAY_TYPE(imap_arg_list) root_list; ARRAY_TYPE(imap_arg_list) *cur_list; struct imap_arg *list_arg; + unsigned int list_count; enum arg_parse_type cur_type; size_t cur_pos; /* parser position in input buffer */ @@ -70,7 +72,7 @@ struct imap_parser { struct imap_parser * imap_parser_create(struct istream *input, struct ostream *output, size_t max_line_size, - const struct imap_parser_params *params ATTR_UNUSED) + const struct imap_parser_params *params) { struct imap_parser *parser; @@ -81,6 +83,10 @@ imap_parser_create(struct istream *input, struct ostream *output, parser->input = input; parser->output = output; parser->max_line_size = max_line_size; + if (params != NULL && params->list_count_limit > 0) + parser->list_count_limit = params->list_count_limit; + else + parser->list_count_limit = UINT_MAX; p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT); parser->cur_list = &parser->root_list; @@ -122,6 +128,7 @@ void imap_parser_reset(struct imap_parser *parser) p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT); parser->cur_list = &parser->root_list; parser->list_arg = NULL; + parser->list_count = 0; parser->cur_type = ARG_PARSE_NONE; parser->cur_pos = 0; @@ -210,6 +217,12 @@ static bool imap_parser_close_list(struct imap_parser *parser) parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; return FALSE; } + if (parser->list_count >= parser->list_count_limit) { + parser->error_msg = "Too many '('"; + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + return FALSE; + } + parser->list_count++; arg = imap_arg_create(parser); arg->type = IMAP_ARG_EOL; diff --git a/src/lib-imap/imap-parser.h b/src/lib-imap/imap-parser.h index 16ef2c7..3832cc9 100644 --- a/src/lib-imap/imap-parser.h +++ b/src/lib-imap/imap-parser.h @@ -39,6 +39,12 @@ enum imap_parser_error { }; struct imap_parser_params { + /* How many open lists ('(' chars) to allow before faililng the parsing. + 0 means unlimited. This is mainly used to prevent excessive memory + usage in imap-login process. In imap process there are many other + ways to increase memory usage, so we let the max_line_size be the + only limit. */ + unsigned int list_count_limit; }; struct imap_parser; diff --git a/src/lib-imap/test-imap-parser.c b/src/lib-imap/test-imap-parser.c index cff2b38..a1347ac 100644 --- a/src/lib-imap/test-imap-parser.c +++ b/src/lib-imap/test-imap-parser.c @@ -2,6 +2,7 @@ #include "lib.h" #include "istream.h" +#include "istream-chain.h" #include "imap-parser.h" #include "test-common.h" @@ -79,6 +80,50 @@ static void test_imap_parser_partial_list(void) test_end(); } +static void test_imap_parser_list_limit(void) +{ + struct { + const char *input; + int ret; + } tests[] = { + { "(())\r\n", 1 }, + { "((()))\r\n", -1 }, + }; + struct istream_chain *chain; + struct istream *chain_input; + struct imap_parser *parser; + const struct imap_arg *args; + + test_begin("imap parser list limit"); + struct imap_parser_params params = { + .list_count_limit = 2, + }; + + for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { + chain_input = i_stream_create_chain(&chain, SIZE_MAX); + parser = imap_parser_create(chain_input, NULL, 1024, ¶ms); + + for (unsigned int j = 0; j < 2; j++) { + struct istream *input = + test_istream_create(tests[i].input); + i_stream_chain_append(chain, input); + i_stream_unref(&input); + + (void)i_stream_read(chain_input); + + test_assert_cmp(imap_parser_read_args(parser, 0, 0, &args), ==, tests[i].ret); + /* skip over CRLF */ + i_stream_skip(chain_input, i_stream_get_data_size(chain_input)); + + /* make sure parser reset works */ + imap_parser_reset(parser); + } + imap_parser_unref(&parser); + i_stream_destroy(&chain_input); + } + test_end(); +} + static void test_imap_parser_read_tag_cmd(void) { enum read_type { @@ -155,6 +200,7 @@ int main(void) static void (*const test_functions[])(void) = { test_imap_parser_crlf, test_imap_parser_partial_list, + test_imap_parser_list_limit, test_imap_parser_read_tag_cmd, NULL }; -- 2.30.2